package com.neo.tiny.bpm.service.process.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.text.StrFormatter;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.annotations.VisibleForTesting;
import com.neo.tiny.admin.api.dept.DeptApi;
import com.neo.tiny.admin.api.dept.PostApi;
import com.neo.tiny.admin.api.dict.DictDataApi;
import com.neo.tiny.admin.api.permission.PermissionApi;
import com.neo.tiny.admin.api.permission.RoleApi;
import com.neo.tiny.admin.api.user.AdminUserApi;
import com.neo.tiny.admin.dto.dept.SysDeptDTO;
import com.neo.tiny.admin.dto.user.UserInfoDTO;
import com.neo.tiny.bpm.entity.BpmTaskAssignRuleDO;
import com.neo.tiny.bpm.entity.BpmUserGroupDO;
import com.neo.tiny.bpm.enums.DictTypeConstants;
import com.neo.tiny.bpm.enums.task.BpmTaskAssignRuleTypeEnum;
import com.neo.tiny.bpm.flowable.core.script.BpmTaskAssignScript;
import com.neo.tiny.bpm.mapper.BpmTaskAssignRuleMapper;
import com.neo.tiny.bpm.service.process.BpmModelService;
import com.neo.tiny.bpm.service.process.BpmProcessDefinitionService;
import com.neo.tiny.bpm.service.process.BpmTaskAssignRuleService;
import com.neo.tiny.bpm.service.process.BpmUserGroupService;
import com.neo.tiny.bpm.vo.rule.BpmTaskAssignRuleCreateReqVO;
import com.neo.tiny.bpm.vo.rule.BpmTaskAssignRuleRespVO;
import com.neo.tiny.bpm.vo.rule.BpmTaskAssignRuleUpdateReqVO;
import com.neo.tiny.common.constant.ErrorCodeConstants;
import com.neo.tiny.common.enums.CommonStatusEnum;
import com.neo.tiny.common.exception.WebApiException;
import com.neo.tiny.flowable.core.util.FlowableUtils;
import com.neo.tiny.query.LambdaQueryWrapperBase;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static cn.hutool.core.text.StrFormatter.format;

/**
 * @author yqz
 * @Description: Bpm 任务规则表
 * @date 2022-10-04 16:02:43
 */
@Slf4j
@Service
public class BpmTaskAssignRuleServiceImpl extends ServiceImpl<BpmTaskAssignRuleMapper, BpmTaskAssignRuleDO> implements BpmTaskAssignRuleService {

    @Resource
    private BpmTaskAssignRuleMapper taskRuleMapper;

    @Resource
    private RoleApi roleApi;

    @Resource
    private DeptApi deptApi;

    @Resource
    private PostApi postApi;

    @Resource
    private AdminUserApi adminUserApi;

    @Resource
    private BpmUserGroupService userGroupService;

    @Resource
    private DictDataApi dictDataApi;

    @Lazy
    @Resource
    private BpmModelService modelService;

    @Lazy
    @Resource
    private BpmProcessDefinitionService processDefinitionService;

    @Resource
    private PermissionApi permissionApi;

    /**
     * 任务分配脚本
     */
    private Map<Long, BpmTaskAssignScript> scriptMap = Collections.emptyMap();

    /**
     * 注入自定义脚本bean，根据枚举值创建自定义脚本实例实现类map集合
     *
     * @param scripts 脚本实现类集合
     */
    @Resource
    public void setScripts(List<BpmTaskAssignScript> scripts) {
        if (CollUtil.isNotEmpty(scripts)) {
            this.scriptMap = scripts.stream().collect(Collectors.toMap(script -> script.getEnum().getId(),
                    Function.identity(), (key1, key2) -> key1));
        }
    }

    @Override
    public List<BpmTaskAssignRuleRespVO> getTaskAssignRuleList(String modelId, String processDefinitionId) {
        // 获得规则
        List<BpmTaskAssignRuleDO> rules = Collections.emptyList();
        BpmnModel model = null;
        if (StrUtil.isNotEmpty(modelId)) {
            rules = getTaskAssignRuleListByModelId(modelId);
            model = modelService.getBpmnByModelId(modelId);
        } else if (StrUtil.isNotEmpty(processDefinitionId)) {
            rules = getTaskAssignRuleListByProcessDefinitionId(processDefinitionId, null);
            model = processDefinitionService.getBpmnModel(processDefinitionId);

        }
        if (model == null) {
            return Collections.emptyList();
        }
        // 获得用户任务，只有用户任务才可以设置分配规则
        List<UserTask> userTasks = FlowableUtils.getBpmnModelElements(model, UserTask.class);
        if (CollUtil.isEmpty(userTasks)) {
            return Collections.emptyList();
        }
        return convert(rules, userTasks);
    }

    public List<BpmTaskAssignRuleRespVO> convert(List<BpmTaskAssignRuleDO> rules, List<UserTask> userTasks) {

        Map<String, BpmTaskAssignRuleDO> ruleMap = rules.stream().collect(Collectors.toMap(BpmTaskAssignRuleDO::getTaskDefinitionKey, Function.identity(), (key1, key2) -> key1));

        return userTasks.stream().map(task -> {

            BpmTaskAssignRuleDO ruleDO = ruleMap.get(task.getId());
            BpmTaskAssignRuleRespVO vo = new BpmTaskAssignRuleRespVO();

            Optional.ofNullable(ruleDO).ifPresent(rule -> {
                BeanUtil.copyProperties(ruleDO, vo);
                Set<Long> options = ruleDO.getOptions();
                if (CollUtil.isNotEmpty(options)) {
                    vo.setOptions(new HashSet<>(options));
                }
            });

            vo.setTaskDefinitionKey(task.getId());
            vo.setTaskDefinitionName(task.getName());
            return vo;
        }).collect(Collectors.toList());

    }

    @Override
    public List<BpmTaskAssignRuleDO> getTaskAssignRuleListByModelId(String modelId) {
        return taskRuleMapper.selectList(new LambdaQueryWrapperBase<BpmTaskAssignRuleDO>()
                .eq(BpmTaskAssignRuleDO::getModelId, modelId)
                .eq(BpmTaskAssignRuleDO::getProcessDefinitionId, BpmTaskAssignRuleDO.PROCESS_DEFINITION_ID_NULL));
    }

    @Override
    public List<BpmTaskAssignRuleDO> getTaskAssignRuleListByProcessDefinitionId(String processDefinitionId, String taskDefinitionKey) {
        return taskRuleMapper.selectList(new LambdaQueryWrapperBase<BpmTaskAssignRuleDO>()
                .eq(BpmTaskAssignRuleDO::getProcessDefinitionId, processDefinitionId)
                .eqIfPresent(BpmTaskAssignRuleDO::getTaskDefinitionKey, taskDefinitionKey));
    }

    @Override
    public Long createTaskAssignRule(BpmTaskAssignRuleCreateReqVO reqVO) {
        // 校验参数
        validTaskAssignRuleOptions(reqVO.getType(), reqVO.getOptions());
        // 校验是否已经配置
        BpmTaskAssignRuleDO existRule = taskRuleMapper.selectOne(new LambdaQueryWrapperBase<BpmTaskAssignRuleDO>().eq(BpmTaskAssignRuleDO::getModelId, reqVO.getModelId()).eq(BpmTaskAssignRuleDO::getProcessDefinitionId, BpmTaskAssignRuleDO.PROCESS_DEFINITION_ID_NULL).eq(BpmTaskAssignRuleDO::getTaskDefinitionKey, reqVO.getTaskDefinitionKey()));
        if (existRule != null) {
            throw new WebApiException(format(ErrorCodeConstants.TASK_ASSIGN_RULE_EXISTS.getMsg(), reqVO.getModelId(), reqVO.getTaskDefinitionKey()));
        }

        // 存储
        BpmTaskAssignRuleDO rule = BeanUtil.copyProperties(reqVO, BpmTaskAssignRuleDO.class);
        Set<Long> options = reqVO.getOptions();
        if (CollUtil.isNotEmpty(options)) {
            rule.setOptions(new HashSet<>(options));
        }
        rule.setProcessDefinitionId(BpmTaskAssignRuleDO.PROCESS_DEFINITION_ID_NULL);
        taskRuleMapper.insert(rule);
        return rule.getId();
    }

    @Override
    public void updateTaskAssignRule(BpmTaskAssignRuleUpdateReqVO reqVO) {
        // 校验参数
        validTaskAssignRuleOptions(reqVO.getType(), reqVO.getOptions());
        // 校验是否存在
        BpmTaskAssignRuleDO existRule = taskRuleMapper.selectById(reqVO.getId());
        if (existRule == null) {
            throw new WebApiException(ErrorCodeConstants.TASK_ASSIGN_RULE_NOT_EXISTS);
        }
        // 只允许修改流程模型的规则
        if (!Objects.equals(BpmTaskAssignRuleDO.PROCESS_DEFINITION_ID_NULL, existRule.getProcessDefinitionId())) {
            throw new WebApiException(ErrorCodeConstants.TASK_UPDATE_FAIL_NOT_MODEL);
        }

        // 执行更新
        BpmTaskAssignRuleDO ruleDO = BeanUtil.copyProperties(reqVO, BpmTaskAssignRuleDO.class);
        taskRuleMapper.updateById(ruleDO);
    }

    @Override
    public void checkTaskAssignRuleAllConfig(String modelId) {
        // 一个用户任务都没配置，所以无需配置规则
        List<BpmTaskAssignRuleRespVO> taskAssignRules = getTaskAssignRuleList(modelId, null);
        if (CollUtil.isEmpty(taskAssignRules)) {
            return;
        }
        // 校验未配置规则的任务

        taskAssignRules.forEach(rule -> {
            if (CollUtil.isEmpty(rule.getOptions())) {
                throw new WebApiException(StrFormatter.format(ErrorCodeConstants.MODEL_DEPLOY_FAIL_TASK_ASSIGN_RULE_NOT_CONFIG.getMsg(), rule.getTaskDefinitionName()));
            }
        });
    }

    @Override
    public boolean isTaskAssignRulesEquals(String modelId, String processDefinitionId) {
        // 调用 VO 接口的原因是，过滤掉流程模型不需要的规则
        List<BpmTaskAssignRuleRespVO> modelRules = getTaskAssignRuleList(modelId, null);
        List<BpmTaskAssignRuleRespVO> processInstanceRules = getTaskAssignRuleList(null, processDefinitionId);
        if (modelRules.size() != processInstanceRules.size()) {
            return false;
        }

        // 遍历，匹配对应的规则
        Map<String, BpmTaskAssignRuleRespVO> processInstanceRuleMap = processInstanceRules.stream().collect(Collectors.toMap(BpmTaskAssignRuleRespVO::getTaskDefinitionKey, Function.identity(), (key1, key2) -> key1));

        for (BpmTaskAssignRuleRespVO modelRule : modelRules) {
            BpmTaskAssignRuleRespVO processInstanceRule = processInstanceRuleMap.get(modelRule.getTaskDefinitionKey());
            if (processInstanceRule == null) {
                return false;
            }
            if (!ObjectUtil.equals(modelRule.getType(), processInstanceRule.getType()) || !ObjectUtil.equal(modelRule.getOptions(), processInstanceRule.getOptions())) {
                return false;
            }
        }
        return true;
    }

    @Override
    public void copyTaskAssignRules(String fromModelId, String toProcessDefinitionId) {
        List<BpmTaskAssignRuleRespVO> rules = getTaskAssignRuleList(fromModelId, null);
        if (CollUtil.isEmpty(rules)) {
            return;
        }
        // 开始复制
        List<BpmTaskAssignRuleDO> newRules = rules.stream().map(vo -> {

            BpmTaskAssignRuleDO ruleDO = new BpmTaskAssignRuleDO();
            BeanUtil.copyProperties(vo, ruleDO);

            Set<Long> options = vo.getOptions();
            if (options != null) {
                ruleDO.setOptions(new HashSet<>(options));
                ruleDO.setOptions(options);
            }
            return ruleDO;
        }).collect(Collectors.toList());

        newRules.forEach(rule -> {
            rule.setId(null);
            rule.setProcessDefinitionId(toProcessDefinitionId);
            rule.setCreateTime(null);
            rule.setUpdateTime(null);
        });
        this.saveBatch(newRules);
    }

    /**
     * TODO 忽略数据权限，不然分配会存在问题
     * <p>
     * 计算当前执行任务的处理人
     *
     * @param execution 执行任务
     * @return 处理人的编号数组
     */
    @Override
    public Set<Long> calculateTaskCandidateUsers(DelegateExecution execution) {
        BpmTaskAssignRuleDO rule = getTaskRule(execution);
        return calculateTaskCandidateUsers(execution, rule);
    }

    @VisibleForTesting
    BpmTaskAssignRuleDO getTaskRule(DelegateExecution execution) {
        List<BpmTaskAssignRuleDO> taskRules = getTaskAssignRuleListByProcessDefinitionId(
                execution.getProcessDefinitionId(),
                execution.getCurrentActivityId());
        if (CollUtil.isEmpty(taskRules)) {
            throw new FlowableException(CharSequenceUtil.format("流程任务({}/{}/{}) 找不到符合的任务规则", execution.getId(), execution.getProcessDefinitionId(), execution.getCurrentActivityId()));
        }
        if (taskRules.size() > 1) {
            throw new FlowableException(CharSequenceUtil.format("流程任务({}/{}/{}) 找到过多任务规则({})", execution.getId(), execution.getProcessDefinitionId(), execution.getCurrentActivityId()));
        }
        return taskRules.get(0);
    }

    @VisibleForTesting
    Set<Long> calculateTaskCandidateUsers(DelegateExecution execution, BpmTaskAssignRuleDO rule) {
        Set<Long> assigneeUserIds = null;
        if (Objects.equals(BpmTaskAssignRuleTypeEnum.ROLE.getType(), rule.getType())) {
            assigneeUserIds = calculateTaskCandidateUsersByRole(rule);
        } else if (Objects.equals(BpmTaskAssignRuleTypeEnum.DEPT_MEMBER.getType(), rule.getType())) {
            assigneeUserIds = calculateTaskCandidateUsersByDeptMember(rule);
        } else if (Objects.equals(BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType(), rule.getType())) {
            assigneeUserIds = calculateTaskCandidateUsersByDeptLeader(rule);
        } else if (Objects.equals(BpmTaskAssignRuleTypeEnum.POST.getType(), rule.getType())) {
            assigneeUserIds = calculateTaskCandidateUsersByPost(rule);
        } else if (Objects.equals(BpmTaskAssignRuleTypeEnum.USER.getType(), rule.getType())) {
            assigneeUserIds = calculateTaskCandidateUsersByUser(rule);
        } else if (Objects.equals(BpmTaskAssignRuleTypeEnum.USER_GROUP.getType(), rule.getType())) {
            assigneeUserIds = calculateTaskCandidateUsersByUserGroup(rule);
        } else if (Objects.equals(BpmTaskAssignRuleTypeEnum.SCRIPT.getType(), rule.getType())) {
            assigneeUserIds = calculateTaskCandidateUsersByScript(execution, rule);
        }

        // 移除被禁用的用户
        removeDisableUsers(assigneeUserIds);
        // 如果候选人为空，抛出异常
        if (CollUtil.isEmpty(assigneeUserIds)) {
            log.info("[calculateTaskCandidateUsers][流程任务({}/{}/{}) 任务规则({}) 找不到候选人]", execution.getId(),
                    execution.getProcessDefinitionId(), execution.getCurrentActivityId(), JSONUtil.toJsonStr(rule));
            throw new WebApiException(ErrorCodeConstants.TASK_CREATE_FAIL_NO_CANDIDATE_USER);
        }
        return assigneeUserIds;
    }

    @VisibleForTesting
    void removeDisableUsers(Set<Long> assigneeUserIds) {
        if (CollUtil.isEmpty(assigneeUserIds)) {
            return;
        }
        Map<Long, UserInfoDTO> userMap = adminUserApi.getUserMap(assigneeUserIds);
        assigneeUserIds.removeIf(id -> {
            UserInfoDTO user = userMap.get(id);
            return user == null || !CommonStatusEnum.ENABLE.getStatus().equals(user.getStatus());
        });
    }

    /**
     * 根据角色id集合，查询用户id集合
     *
     * @param rule 规则
     * @return 用户id集合
     */
    private Set<Long> calculateTaskCandidateUsersByRole(BpmTaskAssignRuleDO rule) {
        return permissionApi.getUserRoleIdListByRoleIds(rule.getOptions());
    }

    /**
     * 根据部门id集合获取用户
     *
     * @param rule 规则
     * @return 用户id集合
     */
    private Set<Long> calculateTaskCandidateUsersByDeptMember(BpmTaskAssignRuleDO rule) {
        List<UserInfoDTO> users = adminUserApi.getUsersByDeptIds(rule.getOptions());
        if (CollUtil.isEmpty(users)) {
            return new HashSet<>();
        }
        return users.stream().map(UserInfoDTO::getId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
    }

    /**
     * 根据部门id集合获取部门领导用户集合
     *
     * @param rule 规则
     * @return 用户id集合
     */
    private Set<Long> calculateTaskCandidateUsersByDeptLeader(BpmTaskAssignRuleDO rule) {
        List<SysDeptDTO> depts = deptApi.getDepts(rule.getOptions());
        if (CollUtil.isEmpty(depts)) {
            return new HashSet<>();
        }
        return depts.stream().map(SysDeptDTO::getLeader)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
    }

    /**
     * 根据岗位id，获取用户id
     *
     * @param rule 规则
     * @return 用户id
     */
    private Set<Long> calculateTaskCandidateUsersByPost(BpmTaskAssignRuleDO rule) {
        List<UserInfoDTO> users = adminUserApi.getUsersByPostIds(rule.getOptions());
        if (CollUtil.isEmpty(users)) {
            return new HashSet<>();
        }
        return users.stream().map(UserInfoDTO::getId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
    }

    /**
     * 计算用户id结合
     *
     * @param rule 规则
     * @return 用户id
     */
    private Set<Long> calculateTaskCandidateUsersByUser(BpmTaskAssignRuleDO rule) {
        return rule.getOptions();
    }

    /**
     * 根据用户组id，获取用户id
     *
     * @param rule 规则
     * @return 用户id
     */
    private Set<Long> calculateTaskCandidateUsersByUserGroup(BpmTaskAssignRuleDO rule) {
        List<BpmUserGroupDO> userGroups = userGroupService.getUserGroupList(rule.getOptions());
        Set<Long> userIds = new HashSet<>();
        userGroups.forEach(group -> userIds.addAll(group.getUserIds()));
        return userIds;
    }

    /**
     * 根据自定义脚本，获取用户id
     *
     * @param rule 规则
     * @return 用户id
     */
    private Set<Long> calculateTaskCandidateUsersByScript(DelegateExecution execution, BpmTaskAssignRuleDO rule) {
        // 获得对应的脚本
        List<BpmTaskAssignScript> scripts = new ArrayList<>(rule.getOptions().size());
        rule.getOptions().forEach(id -> {
            BpmTaskAssignScript script = scriptMap.get(id);
            if (script == null) {
                throw new WebApiException(StrUtil.format(ErrorCodeConstants.TASK_ASSIGN_SCRIPT_NOT_EXISTS.getMsg(), id));
            }
            scripts.add(script);
        });
        // 逐个计算任务
        Set<Long> userIds = new HashSet<>();
        scripts.forEach(script -> CollUtil.addAll(userIds, script.calculateTaskCandidateUsers(execution)));
        return userIds;
    }


    /**
     * 校验规则创建参数
     *
     * @param type    规则类型
     * @param options 码值
     */
    private void validTaskAssignRuleOptions(Integer type, Set<Long> options) {
        if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.ROLE.getType())) {
            roleApi.validRoles(options);
        } else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.DEPT_MEMBER.getType()) || Objects.equals(type, BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType())) {
            deptApi.validDepts(options);
        } else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.POST.getType())) {
            postApi.validPosts(options);
        } else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.USER.getType())) {
            adminUserApi.validUsers(options);
        } else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.USER_GROUP.getType())) {
            userGroupService.validUserGroups(options);
        } else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.SCRIPT.getType())) {
            dictDataApi.validDictDatas(DictTypeConstants.TASK_ASSIGN_SCRIPT, CollUtil.trans(options, String::valueOf));
        } else {
            throw new IllegalArgumentException(StrFormatter.format("未知的规则类型({})", type));
        }
    }
}
